Skip to content
This repository was archived by the owner on Jul 14, 2026. It is now read-only.

feat: fix tranche key ordering - #1081

Open
sotnikov-s wants to merge 15 commits into
mainfrom
feat/fix-tranche-key-ordering
Open

feat: fix tranche key ordering#1081
sotnikov-s wants to merge 15 commits into
mainfrom
feat/fix-tranche-key-ordering

Conversation

@sotnikov-s

@sotnikov-s sotnikov-s commented May 7, 2026

Copy link
Copy Markdown
Contributor

this PR:

how to verify this code:

https://www.notion.so/hadron/dex-tk-0-migration-verification-35f85d6b9b1080e28044f4d4f793a285?source=copy_link#36e85d6b9b1080a996c4ef23fe9c7832

@sotnikov-s
sotnikov-s marked this pull request as ready for review May 19, 2026 14:47
jcompagni10
jcompagni10 previously approved these changes May 20, 2026
Comment thread app/upgrades/nextupgrade/upgrades.go Outdated
return nil
}

func reconstructLoExpirations(ctx sdk.Context, k dexkeeper.Keeper) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to be a separate process? Why can't we just iterate through all limit order tranches and then check the tranche directly if it is expiring?

@sotnikov-s sotnikov-s May 21, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found explicit per-storage-branch functions easier to understand and safer: we iterate over the whole bunch of leafs at a time and leave no room for a mistake/misunderstanding. I could do as you suggested but in my opinion it would be a more complex migration code with no perceptible practical benefits like speed or efficiency

Comment thread x/dex/types/limit_order_tranche_key.go Outdated

// NewTrancheKey returns a new tranche key based on the tranche index.
func NewTrancheKey(trancheIdx uint64) string {
return fmt.Sprintf("tk-%020d", trancheIdx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not a big deal, but why not use Uint64ToSortableString? It's a bit more space-efficient. Also, from a UX perspective, trying to count/type a bunch of leading zeros is a PITA.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no strong point against this approach, I also considered it, but preferred the zero-padded format for easier testing and debugging. re UX perspective: 1) I don't think anyone (but me and you in scope of this PR haha) would ever count the zeroes since it's easier and more useful to count the non-zero digits 2) if someone ever needs to get to know their tranche index, would it be easier for them to parse the base36 number prefixed with length?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space efficiency is a good point. you suggest to use tk-Uint64ToSortableString(trancheIdx) instead of tk-020%d, right? do you guys think it worths refactoring?

@swelf19 swelf19 May 26, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree Uint64ToSortableString function, I would even get rid of the tk prefix - it doesnt seem to carry any useful information

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved from zero padded tranche keys to Uint64ToSortableString usage. didn't remove the tk- prefix since it ruined proper lexicographical ordering: there are obsolete keys build with height + block gas usage that also have that base36 format build with Uint64ToSortableString and they all start with 5 as well as the tk-plain-decimal keys do. e.g. there is a tranche user record with tranche key 5ak8d741gqz, and after migration a typical tranche key reconstructed from plain decimal looks like 52ojj9. the former key is older than the latter whereas lexicographic sorting puts the latter one before the former, and this is incorrect and it should be mitigated: older tranche keys must be put before newer ones in sorting/iterating.

also addressed this comment:

It seems to me that this kind of migration should be performed in a numbered module migration rather than in an upgrade handler, just like any storage migration required for the correct operation of the new version of the code.

read about the results and dex module exported states here https://www.notion.so/hadron/dex-tk-0-migration-verification-35f85d6b9b1080e28044f4d4f793a285?source=copy_link#36e85d6b9b1080a996c4ef23fe9c7832

@swelf19

swelf19 commented May 26, 2026

Copy link
Copy Markdown
Contributor

It seems to me that this kind of migration should be performed in a numbered module migration rather than in an upgrade handler, just like any storage migration required for the correct operation of the new version of the code.

@sotnikov-s
sotnikov-s marked this pull request as draft May 27, 2026 16:04
jcompagni10
jcompagni10 previously approved these changes May 29, 2026
@jcompagni10

Copy link
Copy Markdown
Contributor

This all looks good. Maybe add a test to confirm that the 3 types of tranches keys (sortableString, tk-[n], and tk-[sortable-string] all get stored in the correct order. I think it is correct. But a test would be nice.

swelf19
swelf19 previously approved these changes Jun 1, 2026

@swelf19 swelf19 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I understanding correctly that these are breaking changes? If an application stored the tk when it was created, it will unexpectedly receive an error when trying to work with the old tk later?
Does it make sense to add a note or comment about this in the code and mention it in a future release?

TrancheUserKey has a back-link to tk. It seems those need to be updated as well?

@sotnikov-s
sotnikov-s dismissed stale reviews from swelf19 and jcompagni10 via 9c83710 June 1, 2026 17:21
@sotnikov-s

Copy link
Copy Markdown
Contributor Author

This all looks good. Maybe add a test to confirm that the 3 types of tranches keys (sortableString, tk-[n], and tk-[sortable-string] all get stored in the correct order. I think it is correct. But a test would be nice.

done here: 9c83710

@sotnikov-s

Copy link
Copy Markdown
Contributor Author

TrancheUserKey has a back-link to tk. It seems those need to be updated as well?

doesn't this part of the migration address this?

func reconstructLoTrancheUserLists(ctx sdk.Context, k dexKeeper) error {
allUsers := k.GetAllLimitOrderTrancheUser(ctx) // there are only 300-ish entries, so getting all is fine
usersToRemove := make([]dextypes.LimitOrderTrancheUser, 0)
usersToUpdate := make([]dextypes.LimitOrderTrancheUser, 0)
for _, user := range allUsers {
if !strings.HasPrefix(user.TrancheKey, "tk-") {
continue
}
usersToRemove = append(usersToRemove, *user)
trancheIdxStr := strings.TrimPrefix(user.TrancheKey, "tk-")
trancheIdx, err := strconv.ParseUint(trancheIdxStr, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse tranche idx %s: %w", trancheIdxStr, err)
}
user.TrancheKey = dextypes.NewTrancheKey(trancheIdx)
usersToUpdate = append(usersToUpdate, *user)
}
if len(usersToRemove) != len(usersToUpdate) {
return fmt.Errorf("mismatch in LO tranche user keys to remove and update counts: %d != %d", len(usersToRemove), len(usersToUpdate))
}
for _, user := range usersToRemove {
k.RemoveLimitOrderTrancheUser(ctx, &user)
}
for _, user := range usersToUpdate {
k.SetLimitOrderTrancheUser(ctx, &user)
}
ctx.Logger().Info("LO tranche user keys reconstructed", "count", len(usersToUpdate))
return nil
}

@sotnikov-s

Copy link
Copy Markdown
Contributor Author

Am I understanding correctly that these are breaking changes? If an application stored the tk when it was created, it will unexpectedly receive an error when trying to work with the old tk later? Does it make sense to add a note or comment about this in the code and mention it in a future release?

I guess so: some msgs and queries have tranche key as a parameter, so if e.g. a smart contract memorised a plain decimal tranche key e.g. tk-100 and it then tries to do something with that tranche using that stored key, it won't work because the tranche key will become tk-22S after migration. don't think it makes sense adding a comment in a code but a note in a future release makes sense to me

@sotnikov-s
sotnikov-s requested review from jcompagni10 and swelf19 June 2, 2026 06:56
@sotnikov-s
sotnikov-s marked this pull request as ready for review June 2, 2026 06:56
@sotnikov-s

Copy link
Copy Markdown
Contributor Author

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

issue about a newer expiring order can be filled before an older one at the same price because the DEX compares tranche IDs as plain text.

3 participants